package com.cms.manage.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

import com.cms.manage.common.ConmonConfig;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.text.ParseException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @version V2.0
 * @Author: sgy
 * @Description:
 * @date 2018/4/10 16:31
 */
public class JWTUtil {
    private static String stringKey = ConmonConfig.getJwtKey();
    private static Logger logger = Logger.getLogger(JWTUtil.class);

    /**
     * 加密接口
     *
     * @param Map<String, Object>
     *                    prjLable:设置jti(JWT ID)：是JWT的唯一标识，根据业务需要，这个可以设置为一个不重复的值，主要用来作为一次性token,从而回避重放攻击。
     *                    userId: 用户id
     *                    startTime：签发时间
     *                    endTime: 生效终止时间
     *                    minutes: 生效时长
     * @return Map<String, Object>
     * @throws If the data type does not match assertions.
     *            java.text.ParseException
     * @Notes 标准声明
     * iss: jwt签发者;
     * sub: jwt所面向的用户;
     * aud: 接收jwt的一方;
     * exp: jwt的过期时间，这个过期时间必须要大于签发时间;
     * nbf: 定义在什么时间之前，该jwt都是不可用的;
     * iat: jwt的签发时间;
     * jti: jwt的唯一身份标识，主要用来作为一次性token,从而回避重放攻击;
     * @date 2018.04.19
     * @author sgy
     */
    public static String createJWT(Map<String, String> data) throws ParseException {
        logger.info("加密接口初始化参数开始！");
        //指定签名的时候使用的签名算法，也就是header那部分，jjwt已经将这部分内容封装好了。
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        long nowMillis = System.currentTimeMillis();//生成JWT的时间(当前计算机元年毫秒数)
        Date now = new Date(nowMillis);
        String prjLable = UUIDGenerator.getUUID32();
        SecretKey key = generalKey();//生成签名的时候使用的秘钥secret,这个方法本地封装了的，一般可以从本地配置文件中读取，切记这个秘钥不能外露哦。它就是你服务端的私钥，在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
        logger.info("加密接口初始化参数结束！");
        //下面就是在为payload添加各种标准声明和私有声明了
        JwtBuilder builder = Jwts.builder()
                //.setClaims(claims)          //如果有私有声明，一定要先设置这个自己创建的私有的声明，这个是给builder的claim赋值，一旦写在标准的声明赋值之后，就是覆盖了那些标准的声明的
                .setId(prjLable)
                .setIssuedAt(now)           //iat: jwt的签发时间
                .setSubject(data.get("subject"))        //sub(Subject)：代表这个JWT的主体，即它的所有人，这个是一个json格式的字符串，可以存放什么userid，roldid之类的，作为什么用户的唯一标志。
                .signWith(signatureAlgorithm, key);//设置签名使用的签名算法和签名使用的秘钥
        //如果过期时间不为0  则设置过期时间
        long ttlMillis = -1;
        if (StringUtils.isNotBlank(data.get("minutes"))) {
            ttlMillis = Long.parseLong(data.get("minutes")) * 60 * 1000;
        }
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date exp = new Date(expMillis);
            builder.setExpiration(exp);     //设置过期时间
        }
        return builder.compact();
    }

    /**
     * 解密jwt
     *
     * @param jwt
     * @return claims
     * Subject : userId
     * id : prjLable
     * @throws Exception
     */
    public static Claims parseJWT(Map<String, String> data) throws Exception {
        String jwt = data.get("token");
        SecretKey key = generalKey();  //签名秘钥，和生成的签名的秘钥一模一样
        Claims claims = Jwts.parser()  //得到DefaultJwtParser
                .setSigningKey(key)         //设置签名的秘钥
                .parseClaimsJws(jwt).getBody();//设置需要解析的jwt
        return claims;
    }

    /**
     * 解密jwt
     *
     * @param jwt
     * @return
     * @throws java.lang.IllegalArgumentException
     */
    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey key = generalKey();  //签名秘钥，和生成的签名的秘钥一模一样
        Claims claims = Jwts.parser()  //得到DefaultJwtParser
                .setSigningKey(key)         //设置签名的秘钥
                .parseClaimsJws(jwt).getBody();//设置需要解析的jwt
        return claims;
    }

    /**
     * 由字符串生成加密key
     *
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.decodeBase64(stringKey);//本地的密码解码
//        System.out.println(encodedKey);//[B@152f6e2
//        System.out.println(Base64.encodeBase64URLSafeString(encodedKey));
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");// 根据给定的字节数组使用AES加密算法构造一个密钥，使用 encodedKey中的始于且包含 0 到前 leng 个字节这是当然是所有。（后面的文章中马上回推出讲解Java加密和解密的一些算法）
        return key;
    }

    //测试
    public static void main(String[] args) throws Exception {
        //redis token
        Map<String, String> map = new HashMap<String, String>();
        map.put("subject", "149");
        map.put("minutes", "360");
        String ab = createJWT(map);
        System.out.println("token:" + ab);

        Claims claims = parseJWT(ab);
        System.out.println("claims = " + claims);
        System.out.println(claims.getSubject());
    }
}
